package com.demo.job.anno.utils;

import com.demo.job.anno.annotation.Job;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author peter
 * @date 2022/4/11 0:27
 */
public class JobSpringExecutor implements ApplicationContextAware, SmartInitializingSingleton {

    private static final Logger logger = LoggerFactory.getLogger(JobSpringExecutor.class);

    private String address;
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    private ApplicationContext applicationContext;

    /**
     * implements SmartInitializingSingleton
     */
    @Override
    public void afterSingletonsInstantiated() {
        logger.info(">>>>>>>>>>> implements SmartInitializingSingleton afterSingletonsInstantiated");
        initJobHandlerMethodRepository(applicationContext);
    }

    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init job handler from method
        // 第一个参数，根据class类型筛选出容器中所有子类的名字(剔除掉没有其他别名的类), Object说明是所有的类
        // 第二个参数，是否考虑非单例的bean
        // 第三个参数，是否允许提前初始化
        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);

        // 遍历所有的Bean的名称(如myBiz,不是全路径，不是类名，是bean名称)
        for (String beanDefinitionName : beanDefinitionNames) {
            // 根据名称获得bean
            Object bean = applicationContext.getBean(beanDefinitionName);

            Map<Method, Job> annotatedMethods = null;   // referred to ：org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                // 获取含有 Job 注解的方法
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<Job>() {
                            @Override
                            public Job inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, Job.class);
                            }
                        });
            } catch (Throwable ex) {
                logger.error(">>>>>>>>>>>  method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
            }
            if (annotatedMethods==null || annotatedMethods.isEmpty()) {
                continue;
            }

            for (Map.Entry<Method, Job> methodJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodJobEntry.getKey();
                Job job = methodJobEntry.getValue();
                // regist
                registJobHandler(job, bean, executeMethod);
            }
        }
    }

    protected void registJobHandler(Job job, Object bean, Method executeMethod){
        if (job == null) {
            return;
        }

        String name = job.value();
        //make and simplify the variables since they'll be called several times later
        Class<?> clazz = bean.getClass();
        String methodName = executeMethod.getName();
        if (name.trim().length() == 0) {
            throw new RuntimeException(">>>>>>>>>>> method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
        }

        executeMethod.setAccessible(true);

        // init and destroy
        Method initMethod = null;
        Method destroyMethod = null;

        if (job.init().trim().length() > 0) {
            try {
                initMethod = clazz.getDeclaredMethod(job.init());
                initMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }
        if (job.destroy().trim().length() > 0) {
            try {
                destroyMethod = clazz.getDeclaredMethod(job.destroy());
                destroyMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(">>>>>>>>>>>  method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }

        // registry jobhandler
        registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
    }

    // ---------------------- job handler repository ----------------------
    private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
    public static IJobHandler loadJobHandler(String name){
        return jobHandlerRepository.get(name);
    }
    public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
        logger.info(">>>>>>>>>>> register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);
        return jobHandlerRepository.put(name, jobHandler);
    }

    /**
     * implements ApplicationContextAware
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        logger.info(">>>>>>>>>>> setApplicationContext success");
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }


}
